<?php

# ajax -- Process AJAX requests.

#use Giocondo as G;

Ajax::validate_input();

# DEBUG
# Simulate random network latency.
#usleep(rand(0,2000000));

# DEBUG
#Error::trigger('Processing AJAX request'
#	. ($Giocondo_e == 1 ? sprintf(" with value '%s'", Ajax::$v) : ''));

# Make session variables global.  Extract must be executed in global scope 
# otherwise session variables are imported as locals not globals.  This is why 
# this code hasn't be encapsulated in a static method of the Ajax class.
# TODO: forcing the client code to use the registry pattern would solve this issue.
# We use references here because they are more efficient.
# Note that for security and optimization we delay extraction until necessary.
extract($_SESSION, EXTR_REFS);

Ajax::update_value();

# Execute user callback using a variable function.
if (Ajax::callback()) {
	# DEBUG
	# Logs the executed command.
	#Error::trigger('');

	# If there is a parse error in the evaluated code, eval() returns FALSE and 
	# execution of the following code continues normally.
	# It is not possible to catch a parse error in eval() using 
	# set_error_handler().
	# In case of a fatal error in the evaluated code, the whole script exits.
	# (extracted from eval PHP manual page)

	# Disable temporarily parse error display. 
	Error::parse(FALSE);
	if ($Giocondo_e == 2) {
		// TODO
	}
	$Giocondo_result = eval($Giocondo_command);
	Error::parse(TRUE);

	Ajax::handle_error();
}

# There can be a response even if there are no callbacks
# (e.g. errors and notices).
Ajax::response();

# Exit program normally.
exit;

# namespace Giocondo;

class Ajax {
	static public $v, $widget;

	static public function validate_input() {
		global $Giocondo_id, $Giocondo_e, $Giocondo_e_name;

		$filters = array(
			'id' => array(
				'filter'  => FILTER_VALIDATE_INT,
				'options' => array(
					'min_range' => 1
				)
			),
			'e' => FILTER_VALIDATE_INT,
			'v' => FILTER_DEFAULT
		);

		$input = filter_input_array(INPUT_POST, $filters);

		# Validate widget id.
		if (is_null($Giocondo_id = $input['id'])) {
			Error::trigger(sprintf("Invalid widget id '%s'",
				$_POST['id']), E_USER_ERROR);
		}
		if (! isset($_SESSION['Giocondo_wid_idx'][$Giocondo_id])) {
			Error::trigger(sprintf('Unknown widget id %u',
				$Giocondo_id), E_USER_ERROR);
		}

		# Validate event.
		if (is_null($Giocondo_e = $input['e'])) {
			Error::trigger(sprintf('Missing event code for widget id %u',
				$Giocondo_id), E_USER_ERROR);
		}
		if ($Giocondo_e === false) {
			Error::trigger(sprintf("Invalid event code '%s' for widget id %u",
				$_POST['e'], $Giocondo_id), E_USER_ERROR);
		}

		# Lookup table to convert event codes. Client-side JavaScript transmits
		# event codes instead of Giocondo event name strings to optimize.
		$event_name=array('onClicked', 'onChanged', 'onDelete');

		# Convert event code to name.
		# Will not issue a notice if the index $Giocondo_e doesn't exist.
		$Giocondo_e_name = @$event_name[$Giocondo_e];
		if (! isset($Giocondo_e_name)) {
			Error::trigger("Unknown widget event code $Giocondo_e" .
				"	for widget id $Giocondo_id", E_USER_ERROR);
		}

		# Acquire new widget value for hidden event onChanged.
		# TODO: if v is transmitted only for e == 1, e could be omitted for simplicity.
		if ($Giocondo_e == 1 && is_null(self::$v = $input['v'])) {
			Error::trigger("Missing value for event code $Giocondo_e" .
				" and widget id $Giocondo_id", E_USER_ERROR);
		}
	}

	static public function update_value() {
		global $Giocondo_id, $Giocondo_wid_idx,
			$Giocondo_e_name, $Giocondo_callback, $Giocondo_e;

		self::$widget = $Giocondo_wid_idx[$Giocondo_id];
		$Giocondo_callback = self::$widget->chas($Giocondo_e_name);
		if ($Giocondo_e == 1) {
			# Update value.
			if (self::$widget->setValue(self::$v)) {
				Error::trigger("Invalid value update request for widget id $Giocondo_id",
					E_USER_ERROR);
			}
		} else if (! $Giocondo_callback) {
			Error::trigger("Invalid widget event $Giocondo_e_name" .
				" for widget id $Giocondo_id", E_USER_ERROR);
		}
	}

	static public function callback() {
		global $Giocondo_callback, $Giocondo_command, $Giocondo_e_name;

		return ($Giocondo_callback &&
			($Giocondo_command = self::$widget->$Giocondo_e_name()) != '');
	}

	static public function handle_error() {
		global $Giocondo_result;

		if ($Giocondo_result === FALSE) {
			# Show widget id and event for parse errors in evaluted callback.
			$error = error_get_last();
			# We don't use Error::trigger here because parse errors are not 
			# triggerable, we call directly our custom error handler.
			Error::handler(E_PARSE, $error['message'], $error['file'], $error['line']);
		}
	}

	# Return response to an event or callback.
	static public function response() {
		# Display all newly created windows with their contents.
		Window::display_new();

		# We need this call to register new globals.
		update_state();
	}
}

# End of file
# vim: set ts=2 sw=2 noet:
?>
